﻿using System.IO;
using System.Text;

namespace H.PK
{
    public class HPackInfo
    {
        /// <summary>
        /// 包名称
        /// </summary>
        public readonly string PackageName;

        /// <summary>
        /// 程序集字节码
        /// </summary>
        public readonly byte[] AssemblyBytes;

        /// <summary>
        /// 程序集符号字节码
        /// </summary>
        public readonly byte[] SymbolBytes;

        /// <summary>
        /// 初始化一个包的内存对象
        /// </summary>
        /// <param name="packageName"></param>
        /// <param name="entryType"></param>
        /// <param name="assemblyBytes"></param>
        /// <param name="symbolBytes"></param>
        private HPackInfo(string packageName, byte[] assemblyBytes, byte[] symbolBytes)
        {
            PackageName = packageName;
            AssemblyBytes = assemblyBytes;
            SymbolBytes = symbolBytes;
        }


        /// <summary>
        /// 新建一个HPackInfo对象
        /// </summary>
        /// <param name="assemblyFile">程序集文件</param>
        /// <param name="entryType">程序集入口</param>
        /// <param name="containSymbol">是否需要包含符号文件</param>
        public HPackInfo(string assemblyFile, bool containSymbol)
        {
            PackageName = Path.GetFileNameWithoutExtension(assemblyFile);

            AssemblyBytes = File.ReadAllBytes(assemblyFile);

            string symbolFile = Path.ChangeExtension(assemblyFile, "pdb");
            if (containSymbol && File.Exists(symbolFile))
            {
                SymbolBytes = File.ReadAllBytes(symbolFile);
            }
        }


        /// <summary>
        /// 从字节数组创建包实例
        /// </summary>
        /// <param name="bytes">reader</param>
        /// <returns></returns>
        /// <exception cref="IOException"></exception>
        public static HPackInfo CreateInstance(BinaryReader reader)
        {
            #region 读取密钥
            byte[] keys = new byte[HEncryption.KeySize];
            if (reader.Read(keys, 0, HEncryption.KeySize) != HEncryption.KeySize)
            {
                throw new IOException("文件已损坏");
            }
            HEncryption encryption = new HEncryption(keys);
            #endregion

            byte[] tmpBytes = new byte[1 << 15];  //预先分配32K的缓冲区

            #region 读取包名称
            int tmpLength = reader.ReadInt32();

            string packageName;
            if (tmpLength > 0)
            {
                if (reader.Read(EnsureSize(ref tmpBytes, tmpLength), 0, tmpLength) != tmpLength)
                {
                    throw new IOException("文件已损坏");
                }
                HEncryption.XorBytes(tmpBytes, 0, tmpLength, keys[0]);
                packageName = Encoding.UTF8.GetString(tmpBytes, 0, tmpLength);
            }
            else
            {
                throw new IOException("文件已损坏");
            }
            #endregion

            #region 读取程序集字节码
            tmpLength = reader.ReadInt32(); //AssemblyBytes
            byte[] assemblyBytes = null;
            if (tmpLength > 0)
            {
                if (reader.Read(EnsureSize(ref tmpBytes, tmpLength), 0, tmpLength) != tmpLength)
                {
                    throw new IOException("文件已损坏");
                }
                assemblyBytes = encryption.Decode(tmpBytes, 0, tmpLength);
            }

            #endregion

            #region 读取程序集符号字节码
            tmpLength = reader.ReadInt32(); //SymbolBytes
            byte[] symbolBytes = null;
            if (tmpLength > 0)
            {
                if (reader.Read(EnsureSize(ref tmpBytes, tmpLength), 0, tmpLength) != tmpLength)
                {
                    throw new IOException("文件已损坏");
                }
                symbolBytes = encryption.Decode(tmpBytes, 0, tmpLength);
            }

            #endregion

            return new HPackInfo(packageName, assemblyBytes, symbolBytes);
        }

        /// <summary>
        /// 从字节数组创建包实例
        /// </summary>
        /// <param name="bytes">hpk包的字节数组</param>
        /// <returns>返回一个实例</returns>
        /// <exception cref="IOException"></exception>
        public static HPackInfo CreateInstance(byte[] bytes)
        {
            return CreateInstance(new BinaryReader(new MemoryStream(bytes)));
        }

        /// <summary>
        /// 确保一个缓冲区的大小,如果缓冲区的大小不足,则自动扩大缓冲区
        /// </summary>
        /// <param name="bytes"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        private static byte[] EnsureSize(ref byte[] bytes, int size)
        {
            if (bytes == null || bytes.Length < size)
            {
                bytes = new byte[size];
            }
            return bytes;
        }

        /// <summary>
        /// 将当前的包信息写入到文件中
        /// </summary>
        /// <param name="file">希望写入的文件的路径</param>
        public void Write(string file)
        {
            using (BinaryWriter writer = new BinaryWriter(new FileStream(file, FileMode.Create)))
            {
                HEncryption encryption = new HEncryption();

                #region 写入加密密钥
                byte[] key = encryption.GetKey();
                writer.Write(key);
                #endregion

                #region 写入包名称
                byte[] tmpBytes = Encoding.UTF8.GetBytes(PackageName);
                writer.Write(tmpBytes.Length);
                HEncryption.XorBytes(tmpBytes, 0, tmpBytes.Length, key[0]);
                writer.Write(tmpBytes);
                #endregion

                #region 写入程序集
                tmpBytes = encryption.Encode(AssemblyBytes, 0, AssemblyBytes.Length);
                writer.Write(tmpBytes.Length);
                writer.Write(tmpBytes);
                #endregion

                #region 写入程序集符号字节码
                if (SymbolBytes != null)
                {
                    tmpBytes = encryption.Encode(SymbolBytes, 0, SymbolBytes.Length);
                    writer.Write(tmpBytes.Length);
                    writer.Write(tmpBytes);
                }
                else
                {
                    writer.Write(0);
                }
                #endregion
            }
        }
    }
}
